package com.sneaker.shower.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.sneaker.shower.common.BizException;
import com.sneaker.shower.common.ThreadLocalCache;
import com.sneaker.shower.entity.domain.order.PayOrderDO;
import com.sneaker.shower.entity.domain.order.SubOrderDO;
import com.sneaker.shower.entity.domain.order.discount.CouponDO;
import com.sneaker.shower.entity.domain.order.discount.CouponTransactionDO;
import com.sneaker.shower.entity.domain.user.UserDO;
import com.sneaker.shower.entity.domain.user.UserLevelDO;
import com.sneaker.shower.entity.domain.user.UserRemarkDO;
import com.sneaker.shower.entity.dto.*;
import com.sneaker.shower.entity.enums.SubOrderStatusEnum;
import com.sneaker.shower.entity.enums.UserTypeEnum;
import com.sneaker.shower.mapper.UserMapper;
import com.sneaker.shower.service.*;
import lombok.extern.slf4j.Slf4j;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Base64Utils;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    private final SmsService smsService;
    private final UserLevelService userLevelService;
    private final SubOrderService subOrderService;
    private final PayService payService;
    private final UserRemarkService userRemarkService;
    private final WxMaService wxMaService;
    private final FileService fileService;
    private final CouponService couponService;
    private final CouponTransactionService transactionService;
    private ExpiringMap<String, String> expiringMap;

    @Value("${member.showCharge}")
    private Boolean showCharge;

    @Autowired
    public UserServiceImpl(SmsService smsService,
                           UserLevelService userLevelService,
                           UserRemarkService userRemarkService,
                           @Lazy SubOrderService subOrderService,
                           WxMaService wxMaService,
                           FileService fileService,
                           @Lazy PayService payService,
                           CouponService couponService,
                           CouponTransactionService transactionService) {
        this.smsService = smsService;
        this.userLevelService = userLevelService;
        this.subOrderService = subOrderService;
        this.userRemarkService = userRemarkService;
        this.wxMaService = wxMaService;
        this.fileService = fileService;
        this.payService = payService;
        this.couponService = couponService;
        this.transactionService = transactionService;
    }

    @PostConstruct
    public void init() {
        expiringMap = ExpiringMap.builder().expiration(1, TimeUnit.MINUTES).expirationPolicy(ExpirationPolicy.CREATED).build();
    }

    @Override
    public Boolean register(String openId, String verifyCode, String phone) {
        Optional.ofNullable(verifyCode).filter(e -> StringUtils.equals(expiringMap.get(phone), e)).orElseThrow(() -> new BizException("验证码错误"));
        expiringMap.remove(phone);
        UserDO userDO = this.getOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getOpenId, openId));
        UserDO update = new UserDO();
        update.setId(userDO.getId());
        update.setGuid(userDO.getGuid());
        update.setPhone(phone);
        update.setUserType(UserTypeEnum.NORMAL.getCode());
        this.updateById(userDO);
        return true;
    }

    @Override
    public UserChargeRespDTO rolling(String guid) {
        PayOrderDO payOrderDO = payService.getById(guid);
        if (payOrderDO.getStatus() != 1) {
            return UserChargeRespDTO.builder()
                    .respCode(Integer.valueOf(payOrderDO.getStatus()))
                    .errorMsg(payOrderDO.getErrorMsg())
                    .build();
        }
        UserDO byId = this.getById(payOrderDO.getUserGuid());
        return UserChargeRespDTO.builder()
                .respCode(Integer.valueOf(payOrderDO.getStatus()))
                .userType(UserTypeEnum.getByCode(byId.getUserType()).getMessage())
                .amount(byId.getAmount())
                .build();
    }

    @Override
    public WxPayParamDTO charge(Integer type) {
        UserTypeEnum typeEnum = UserTypeEnum.getByCode(type);
        String organizationGuid = ThreadLocalCache.get().getOrganizationGuid();
        UserDTO dto = ThreadLocalCache.get();
        if (typeEnum == null) {
            throw new BizException("无效的用户类型");
        }
        PayOrderDO payOrderDO = new PayOrderDO();
        payOrderDO.setStatus(Byte.valueOf("0"));
        payOrderDO.setAttach(String.valueOf(type));
        payOrderDO.setType(Byte.valueOf("1"));
        payOrderDO.setGuid(IdWorker.getId());
        payOrderDO.setUserGuid(Long.valueOf(dto.getGuid()));
        payOrderDO.setOrganizationGuid(Long.parseLong(organizationGuid));
        payOrderDO.setActuallyPayFee(typeEnum.getPrice());
        payOrderDO.setTotalFee(typeEnum.getPrice());
        payOrderDO.setOrderPrice(typeEnum.getPrice());
        payOrderDO.setGmtCreate(LocalDateTime.now());
        payOrderDO.setGmtModified(LocalDateTime.now());
        payOrderDO.setVersion(0);
        payService.save(payOrderDO);
        UserDO userDO = new UserDO();
        userDO.setGuid(Long.parseLong(dto.getGuid()));
        userDO.setVersion(dto.getUserVersion());
        UserLevelDO userLevelDO = userLevelService.getOne(
                new LambdaQueryWrapper<UserLevelDO>()
                        .eq(UserLevelDO::getUserGuid, userDO.getGuid())
                        .eq(UserLevelDO::getLevelCode, type));
        return WxPayParamDTO.INSTANCE(payOrderDO, userLevelDO, userDO, null);
    }

    @Override
    @Transactional
    public void updateUserLevel(PayOrderDO payOrderDO, WxPayParamDTO wxPayParamDTO) {
        Pair<UserLevelDO, BigDecimal> userLevelBigDecimalPair;
        userLevelBigDecimalPair = userLevelService.updateUserLevel(payOrderDO, wxPayParamDTO);
        UserLevelDO left = userLevelBigDecimalPair.getLeft();
        BigDecimal right = userLevelBigDecimalPair.getRight();
        UserDO userDO = new UserDO();
        userDO.setGuid(payOrderDO.getUserGuid());
        userDO.setAmount(right);
        userDO.setUserType(left.getLevelCode());
        userDO.setVersion(wxPayParamDTO.getUserVersion());
        if (!updateById(userDO)) {
            log.error("订单支付失败， 用户信息已发生变化");
            throw new BizException("订单支付失败， 用户信息发生变化");
        }
    }

    @Override
    public Boolean updateUserRemarkName(UserDTO userDTO) {
        UserDO db = this.getById(Long.parseLong(userDTO.getGuid()));
        if (db == null) {
            throw new BizException("用户不存在");
        }
        UserDTO kfDTO = ThreadLocalCache.get();
        String organizationGuid = kfDTO.getOrganizationGuid();
        UserRemarkDO one = userRemarkService.getOne(new LambdaQueryWrapper<UserRemarkDO>()
                .eq(UserRemarkDO::getUserGuid, db.getGuid())
                .eq(UserRemarkDO::getOrganizationGuid, Long.parseLong(organizationGuid)));
        if (one == null) {
            one = new UserRemarkDO();
            one.setGuid(IdWorker.getId());
            one.setOrganizationGuid(Long.parseLong(organizationGuid));
            one.setUserGuid(db.getGuid());
        }
        one.setRemarkName(Base64Utils.encodeToString(userDTO.getRemarkName().getBytes()));
        return userRemarkService.saveOrUpdate(one);
    }

    @Override
    public void userPay(UserLevelDO userLevelDO, Long userGuid, Integer userVersion) {
        Pair<UserLevelDO, BigDecimal> userLevelBigDecimalPair = userLevelService.updateUserPayLevel(userLevelDO, userGuid);
        UserDO userDO = new UserDO();
        userDO.setGuid(userGuid);
        userDO.setAmount(userLevelBigDecimalPair.getRight());
        userDO.setUserType(userLevelBigDecimalPair.getLeft().getLevelCode());
        userDO.setVersion(userVersion);
        if (!updateById(userDO)) {
            throw new BizException("用户信息已发生变化 请刷新后重试");
        }
    }

    @Override
    public Boolean verifyCode(String phone) {

        double d = Math.random();
        String verifyCode = ((int) (d * 10 * 10 * 10 * 10)) + "";
        Map<String, String> param = new HashMap<>();
        param.put("code", verifyCode);
        expiringMap.put(phone, verifyCode);
        return smsService.send(phone, param);
    }

    @Override
    public MemberInfoDTO getMemberInfo() {
        String guid = ThreadLocalCache.get().getGuid();
        UserDO byId = getById(guid);
        UserTypeEnum byCode = UserTypeEnum.getByCode(byId.getUserType());
        String discount;
        if (BigDecimal.ONE.compareTo(byCode.getDiscount()) == 0) {
            discount = "暂无优惠";
        } else {
            discount = String.format("享 %s 折优惠", byCode.getDiscount().multiply(new BigDecimal("10.0")));
        }
        return MemberInfoDTO.builder()
                .level(byCode.getMessage())
                .amount("当前余额: " + Optional.ofNullable(byId.getAmount()).orElse(BigDecimal.ZERO))
                .discount(discount)
                .build();
    }

    @Override
    public UserPayTypeRespDTO getUserPayType(String orderGuid) {
        SubOrderDO subOrderDO = subOrderService.getById(Long.parseLong(orderGuid));
        if (!Objects.equals(SubOrderStatusEnum.WAIT_FOR_PAYING.getCode(), subOrderDO.getStatus()) &&
                !Objects.equals(SubOrderStatusEnum.PAY_FAILED.getCode(), subOrderDO.getStatus())) {
            log.info("当前订单【{}】，状态【{}】", orderGuid, subOrderDO.getStatus());
            throw new BizException("当前订单无法支付哟！");
        }
        return getPayType(subOrderDO);
    }

    @Override
    public UserPayTypeRespDTO getOfflinePayType(BigDecimal amount) {
        if (amount.compareTo(new BigDecimal("99999999.99")) > 0) {
            throw new BizException("订单金额过大");
        }
        if (amount.compareTo(new BigDecimal("0.01")) < 0) {
            throw new BizException("订单金额过小");
        }
        SubOrderDO subOrderDO = new SubOrderDO();
        subOrderDO.setOrderPrice(amount);
        return getPayType(subOrderDO);
    }

    private UserPayTypeRespDTO getPayType(SubOrderDO subOrderDO) {
        String userGuid = ThreadLocalCache.get().getGuid();
        UserDO userDO = this.getById(Long.parseLong(userGuid));
        List<UserLevelDO> list = userLevelService.list(new LambdaQueryWrapper<UserLevelDO>()
                .eq(UserLevelDO::getUserGuid, userGuid)
                .gt(UserLevelDO::getLevelAmount, BigDecimal.ZERO)
                .orderByDesc(UserLevelDO::getLevelCode));
        UserPayTypeRespDTO userPayTypeRespDTO = new UserPayTypeRespDTO();
        userPayTypeRespDTO.setUserGuid(userGuid);
        userPayTypeRespDTO.setUserVersion(userDO.getVersion());
        List<UserPayTypeDTO> userPayTypeDTOS = Lists.newArrayList();
        if (CollectionUtils.isEmpty(list)) {
            log.info("当前用户无可用会员卡");
            userPayTypeRespDTO.setUserPayTypeDTOS(userPayTypeDTOS);
            return userPayTypeRespDTO;
        }
        // 判断用户是否使用了优惠券 如果则需要减去优惠券折扣后计算金额
        final BigDecimal discountFee;
        if (Objects.equals(Boolean.TRUE, subOrderDO.getUsedCoupon()) && subOrderDO.getCouponGuid() != null) {
            CouponDO couponDO = couponService.getById(subOrderDO.getCouponGuid());
            if (couponDO == null || !couponDO.checkStatus()) {
                discountFee = BigDecimal.ZERO;
            } else {
                List<CouponTransactionDO> userCouponTransaction = transactionService.getUserCouponTransaction(couponDO.getType());
                if (CollectionUtils.isEmpty(userCouponTransaction)) {
                    discountFee = couponDO.getDiscountFee() == null ? BigDecimal.ZERO : couponDO.getDiscountFee();
                } else {
                    discountFee = BigDecimal.ZERO;
                }
            }
        } else {
            discountFee = BigDecimal.ZERO;
        }
        list.forEach(userLevel -> {
            UserPayTypeDTO userPayTypeDTO = new UserPayTypeDTO();
            userPayTypeDTO.setAmount(userLevel.getLevelAmount());
            userPayTypeDTO.setDiscount(userLevel.getLevelDiscount());
            userPayTypeDTO.setPayTypeName(UserTypeEnum.getByCode(userLevel.getLevelCode()).getMessage());
            userPayTypeDTO.setPayTypeGuid(String.valueOf(userLevel.getGuid()));

            BigDecimal orderPrice = subOrderDO.getOrderPrice();
            BigDecimal realPay;
            if (orderPrice.compareTo(discountFee) > 0) {
                realPay = orderPrice.subtract(discountFee)
                        .multiply(userLevel.getLevelDiscount()).setScale(2, BigDecimal.ROUND_HALF_UP);
            } else {
                realPay = BigDecimal.ZERO;
            }
            userPayTypeDTO.setActuallyPayFee(realPay);
            BigDecimal bigDecimal = userLevel.getLevelAmount()
                    .compareTo(realPay) >= 0 ? BigDecimal.ZERO :
                    realPay.subtract(userLevel.getLevelAmount());
            userPayTypeDTO.setNeedPayFee(bigDecimal);
            userPayTypeDTO.setUserLevelVersion(userLevel.getVersion());
            userPayTypeDTOS.add(userPayTypeDTO);
        });
        userPayTypeRespDTO.setUserPayTypeDTOS(userPayTypeDTOS);
        return userPayTypeRespDTO;
    }

    @Override
    public String getInvitedQrCode() {
        UserDTO userDTO = ThreadLocalCache.get();
        return Optional.ofNullable(StringUtils.isBlank(userDTO.getInvitedQrCode()) ? null : userDTO.getInvitedQrCode())
                .orElseGet(() -> {
                    String upload = null;
                    try {
                        String invitedCode = userDTO.getGuid();
                        log.info("用户【{}】生成小程序邀请码：【{}】", userDTO.getGuid(), userDTO.getGuid());
                        upload = fileService.upload(
                                wxMaService.getQrcodeService()
                                        .createWxaCodeUnlimit(invitedCode, "pages/home/main"));
                        UserDO userDO = new UserDO();
                        userDO.setGuid(Long.parseLong(userDTO.getGuid()));
                        userDO.setInvitedQrCodeUrl(upload);
                        this.updateById(userDO);
                    } catch (Exception e) {
                        log.info("生成小程序邀请码发生异常:\n" + e);
                    }
                    return upload;
                });
    }

    @Override
    public TotalPromotionDTO getMyPromotion(PromotionsReqDTO promotionsReqDTO) {
        UserDTO userDTO = ThreadLocalCache.get();
        List<UserDO> userDOList = this.list(
                new LambdaQueryWrapper<UserDO>()
                        .eq(UserDO::getInvitedUserGuid, Long.parseLong(userDTO.getGuid())));
        if (CollectionUtils.isEmpty(userDOList)) {
            return TotalPromotionDTO.DEFAULT;
        }
        Map<Long, UserDO> userMap = userDOList.stream().collect(Collectors.toMap(UserDO::getGuid, Function.identity()));
        List<Long> userGuidList = userDOList.stream().map(UserDO::getGuid).collect(Collectors.toList());
        LambdaQueryWrapper<PayOrderDO> wrapper = new LambdaQueryWrapper<PayOrderDO>()
                .eq(PayOrderDO::getStatus, Byte.valueOf("1"))
                .in(PayOrderDO::getType, Arrays.asList(Byte.valueOf("0"), Byte.valueOf("2")))
                .in(PayOrderDO::getUserGuid, userGuidList)
                .ge(PayOrderDO::getGmtCreate, promotionsReqDTO.getStartDate().atTime(LocalTime.MIN))
                .le(PayOrderDO::getGmtCreate, promotionsReqDTO.getEndDate().atTime(LocalTime.MAX));
        List<PayOrderDO> payOrderDOS = payService.list(wrapper);
        Map<Long, List<PayOrderDO>> longListMap = payOrderDOS.stream()
                .collect(Collectors.groupingBy(PayOrderDO::getUserGuid));

        List<PromotionDTO> promotionDTOS = Lists.newArrayList();
        for (Map.Entry<Long, List<PayOrderDO>> entry : longListMap.entrySet()) {
            Long k = entry.getKey();
            List<PayOrderDO> v = entry.getValue();
            PromotionDTO promotionDTO = new PromotionDTO();
            if (!CollectionUtils.isEmpty(v)) {
                UserDO userDO = userMap.get(k);
                if (userDO != null) {
                    promotionDTO.setUserNickName(new String(Base64Utils.decodeFromString(userDO.getNickName())));
                    promotionDTO.setUserGuid(String.valueOf(k));
                    promotionDTO.setUserImg(userDO.getAvatarUrl());
                    BigDecimal payFee = v.stream().map(o -> Optional.ofNullable(o.getTotalFee()).orElse(BigDecimal.ZERO))
                            .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                    if (payFee.compareTo(BigDecimal.ZERO) > 0) {
                        promotionDTO.setPayFee(payFee);
                        promotionDTOS.add(promotionDTO);
                    }
                }
            }
        }
        if (promotionDTOS.size() == 0) {
            return TotalPromotionDTO.DEFAULT;
        }
        BigDecimal totalFee = promotionDTOS.stream().map(PromotionDTO::getPayFee).reduce(BigDecimal::add).get();
        return TotalPromotionDTO.builder()
                .count(promotionDTOS.size())
                .totalFee(totalFee)
                .promotionDTOS(promotionDTOS)
                .build();
    }

    @Override
    public Boolean showCharge() {
        return showCharge == null ? false : showCharge;
    }
}
